package com.djahva.image;

import javax.imageio.ImageIO;
import javax.imageio.ImageWriteParam;
import javax.imageio.ImageWriter;
import javax.imageio.IIOImage;

import java.io.*;
import java.util.*;
import java.awt.image.BufferedImage;
import java.awt.image.AffineTransformOp;
import java.awt.geom.AffineTransform;
import java.awt.*;

/**
 * Created by IntelliJ IDEA. User: geert Date: Jul 30, 2007 Time: 10:22:10 PM To
 * change this template use File | Settings | File
 * Templates./home/geert/projects
 * /MMSBlog/src/com/storedesk/image/ImageRequest.java
 */
public class ImageRequest {

	BufferedImage image;
	ImageProvider provider;
	String location = "";
	String root = "";
	String img;
	String mime = "image/jpeg";
	private int maxsize = 0;
	private double compression = 0.8;
	private StringBuffer log = new StringBuffer();
	private long startTime = System.currentTimeMillis();
	
	private int maxImageSize;

	String width = null;
	String height = null;
	Map properties = new HashMap();

	public ImageRequest(String location, String img, int maxImageSize) {

		// hack if location is the complete path and img is empty
		// update : always split last item to img
		if (img == null)
			img = "";
		if (!location.endsWith("/") && !img.startsWith("/")) {
			location = location + "/";
		}
		location = location + img;

		int pos = location.lastIndexOf("/");
		if ((pos > 0) && (pos < location.length() - 1)) {
			log("orig location " + location);
			log("split location at pos " + pos);
			img = location.substring(pos);
			location = location.substring(0, pos);
		}
		this.img = img;
		this.location = location;
		setMaxImageSize(maxImageSize);
		log("Root :" + location);
		log("img  :" + img);
		log(new Date().toString());
	}
	
	private void setMaxImageSize(int maxImageSize){
		if(maxImageSize < 0){
			this.maxImageSize = 2 * 1024 * 1024;
		} else {
			this.maxImageSize = maxImageSize;
		}
	}

	public void readFile(File file) throws IOException {
		log("Read properties from file " + file);
		Properties p = new Properties();
		FileInputStream in = new FileInputStream(file);
		p.load(in);
		in.close();
		log("Read properties from file " + p);
		readMap(p);
	}

	public void readXMLFile(File file) throws IOException {
		log("Read properties from file " + file);
		Properties p = new Properties();
		try {
			FileInputStream in = new FileInputStream(file);
			p.loadFromXML(in);
			in.close();
		} catch (NullPointerException e) {
			System.out.println("file " + file);
			e.printStackTrace();
		}
		log("Read properties from file " + p);
		readMap(p);
	}

	public String getLog() {
		return log.toString();
	}

	public void log(String info) {
		log.append((System.currentTimeMillis() - startTime)).append(" \t");
		log.append(info);
		log.append("\r\n<BR/>");
	}

	public void readMap(Map values) {
		for (Iterator iterator = values.keySet().iterator(); iterator.hasNext();) {
			String s = (String) iterator.next();
			Object v = values.get(s);
			String value = v.toString();
			if (v instanceof String[]) {
				value = ((String[]) v)[0];
			}
			setProperty(s, value);
		}
	}

	public String getRoot() {
		return root;
	}

	private void setRoot(String root) {
		this.root = root;
	}

	public String getMime() {
		return mime;
	}

	private void setMime(String mime) {
		this.mime = mime;
	}

	public String getImg() {
		return img;
	}

	private void setImg(String img) {
		this.img = img;
	}

	private void setProperty(String key, String value) {
		if (key.equals("location")) { // && allowed locationchange
			if (value.startsWith("http")) {
				location = value;
			} else if (value.startsWith(".")) {
				location = location + "/" + value;
			} else {
				location = root + value;
			}
		} else {
			properties.put(key.toLowerCase(), value);
		}
	}

	public int getNumber(String value, int defaultValue) {
		return getNumber(value, defaultValue, defaultValue);
	}

	public int getNumber(String value, int defaultValue, int maxValue) {
		if (value == null)
			return defaultValue;
		int result = defaultValue;

		boolean procent = false;
		int index = value.indexOf("proc");
		if (index > -1) {
			value = value.substring(0, index);
			procent = true;
		}
		index = value.indexOf("%");
		if (index > -1) {
			value = value.substring(0, index);
			procent = true;
		}

		try {
			result = Integer.parseInt(value.trim());
		} catch (NumberFormatException ignore) {
		}

		if (procent) {
			result = result * maxValue / 100;
		}
		return result;
	}

	public String getValue(String key) {
		key = key.toLowerCase();
		String value = (String) properties.get(key);
		if (value == null)
			value = "";
		return value;
	}

	private BufferedImage readImage() throws IOException {

		String prov = getValue("imageProvider");
		if (!"".equals(prov)) {
			try {
				provider = (ImageProvider) Class.forName(prov).newInstance();
				log("implement provider " + prov);
			} catch (InstantiationException e) {
				log("Error" + e);
			} catch (IllegalAccessException e) {
				log("Error" + e);
			} catch (ClassNotFoundException e) {
				log("Error" + e);
			}
		}
		if ("".equals(prov)) {
			if (location.toLowerCase().startsWith("http:")) {
				provider = new HttpImageProvider();
			} else {
				provider = new FileImageProvider();
			}
		}
		return provider.getImage(this, location);
	}

	public BufferedImage getImage() throws IOException {
		if (image == null) {
			image = readImage();
			if (image == null)
				throw new IOException("Image " + location + img + " not found");

			int cropWidth = getNumber(getValue("crwidth"), image.getWidth());
			int cropHeight = getNumber(getValue("crheight"), image.getHeight());

			// default crop center
			int x = getNumber(getValue("x"), (image.getWidth() / 2)
					- (cropWidth / 2));
			int y = getNumber(getValue("y"), (image.getHeight() / 2)
					- (cropHeight / 2));

			double rotate = getNumber(getValue("rotate"), 0, 0);
			if (rotate > 0) {
				image = rotate(image, rotate);
			}

			image = crop(image, x, y, cropWidth, cropHeight);

			boolean keepAspectRatio = false;
			if (getValue("keepAspectRatio").equals("true"))
				keepAspectRatio = true;
			if (getValue("AspectRatio").equals("true"))
				keepAspectRatio = true;
			if (getValue("keepAspect").equals("true"))
				keepAspectRatio = true;
			if (getValue("intelliCrop").equals("true"))
				keepAspectRatio = true;
			if (getValue("optimalCrop").equals("true"))
				keepAspectRatio = true;

			int width = getNumber(getValue("width"), 0, image.getWidth());
			int height = getNumber(getValue("height"), 0, image.getHeight());
			
			//grootte van de image beperken
			if(width * height * 3 > maxImageSize)
				throw new IllegalArgumentException("The given dimensions are too large");

			boolean flipHorizontal = false;
			if (getValue("flipHorizontal").equalsIgnoreCase("true"))
				flipHorizontal = true;
			boolean flipVertical = false;
			if (getValue("flipVertical").equalsIgnoreCase("true"))
				flipVertical = true;

			if (keepAspectRatio) {
				double aspectRatio = (double) image.getWidth() / image.getHeight();
				double wratio = (double) width / image.getWidth();
				double hratio = (double) height / image.getHeight();
				double wantedAspectRatio = aspectRatio;

				log("aspectRatio " + aspectRatio);

				if ((width > 0) && (height > 0)) {
					// optimal Crop
					wantedAspectRatio = (double) width / height;
					double ratio = wantedAspectRatio / aspectRatio;

					log("wanted aspect ratio " + wantedAspectRatio);

					if (ratio == 1) {
						// No crop, only resize.
						log("No Crop needed for optimal size");
					} else if (ratio > 1) {
						int h = (int) (image.getHeight() / ratio);
						log("crop height " + h);
						image = crop(image, 0,
								(int) (image.getHeight() - h) / 2, image
										.getWidth(), h);
					} else {
						int w = (int) (image.getWidth() * ratio);
						log("crop width " + w);
						image = crop(image, (image.getWidth() - w) / 2, 0,
								w, image.getHeight());
					}
				} else {
					if ((width == 0) && (height == 0)) {
						width = image.getWidth();
						height = image.getHeight();
					} else {
						if (height == 0) {
							height = (int) (wratio * image.getHeight());
						}
						if (width == 0) {
							width = (int) (hratio * image.getWidth());
						}
					}
				}
			} else {
				if (width == 0)
					width = image.getWidth();
				if (height == 0)
					height = image.getHeight();

			}
			image = transform(image, width, height, flipHorizontal,
					flipVertical, 0);

			// other changes
			// add overlay
			// add border
			int borderWidth = getNumber(getValue("border"), 0, 0);
			if (borderWidth > 0) {
				Color color = Color.black;
				try {
					color = Color.decode(getValue("borderColor"));
				} catch (NumberFormatException ignore) {
				}
				border(borderWidth, color);
			}
			// add text ....
		}
		return image;
	}

	public BufferedImage crop(BufferedImage source, int crx, int cry,
			int crwidth, int crheight) {
		log("crop " + crx + "x" + cry + " " + crwidth + "x" + crheight);
		crx = (crx < 0) ? 0 : crx;
		cry = (cry < 0) ? 0 : cry;
		crwidth = (crwidth <= 0) ? source.getWidth() - crx
				: (crwidth + crx > source.getWidth()) ? source.getWidth() - crx
						- 1 : crwidth;
		crheight = (crheight <= 0) ? source.getHeight() - cry
				: (crheight + cry > source.getHeight()) ? source.getHeight()
						- cry - 1 : crheight;

		source = source.getSubimage(crx, cry, crwidth, crheight);
		return source;
	}

	public BufferedImage rotate(BufferedImage source, double angle) {
		log("Rotate " + angle);

		// rotate, rond het middelpunt van de afbeelding
		AffineTransform trans = AffineTransform.getRotateInstance(angle
				* Math.PI / 180, source.getWidth() / 2, source.getHeight() / 2);

		// todo
		// if angle =- 90 of 270, swap width and height
		// of calculate correct width and height

		AffineTransformOp transop = new AffineTransformOp(trans, null);
		source = transop.filter(source, null);
		return source;
	}

	public BufferedImage transform(BufferedImage source, int width, int height,
			boolean flipHorizontal, boolean flipVertical, double angle) {
		log("Transform " + width + "x" + height + " " + flipHorizontal + " "
				+ flipVertical + " " + angle);
		width = (width <= 0) ? source.getWidth() : width;
		height = (height <= 0) ? source.getHeight() : height;

		double flipx = (double) width / source.getWidth();
		double flipy = (double) height / source.getHeight();
		double transx = 0;
		double transy = 0;

		if (flipHorizontal) {
			flipx = -(double) width / source.getWidth();
			transx -= source.getWidth();
		}

		if (flipVertical) {
			flipy = -(double) height / source.getHeight();
			transy -= source.getHeight();
		}
		// rotate, rond het middelpunt van de afbeelding
		AffineTransform trans = AffineTransform.getRotateInstance(angle
				* Math.PI / 180, width / 2, height / 2);
		trans.concatenate(AffineTransform.getScaleInstance(flipx, flipy));
		trans.translate(transx, transy);

		AffineTransformOp transop = new AffineTransformOp(trans, null);
		source = transop.filter(source, null);
		return source;
	}

	public void border(int borderWidth, Color color) {
		log("add border " + borderWidth + " " + color);
		BufferedImage background = new BufferedImage(image.getWidth()
				+ (2 * borderWidth), image.getHeight() + (2 * borderWidth),
				BufferedImage.TYPE_INT_RGB);
		Graphics2D g = background.createGraphics();
		g.setColor(color);
		g.fillRect(0, 0, background.getWidth(), background.getHeight());
		g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 1));

		g.drawImage(image, borderWidth, borderWidth, image.getWidth(), image
				.getHeight(), null);
		g.dispose();
		image = background;
	}

	public void writeTo(OutputStream os) throws IOException {
		
		ImageWriteParam p;
		ImageWriter iw = null;
		Iterator i = ImageIO.getImageWritersByMIMEType(mime);
		if (i.hasNext()) {
			iw = (ImageWriter) i.next();
		} else {
			i = ImageIO.getImageWritersByMIMEType("image/jpeg");
			iw = (ImageWriter) i.next();
		}

		p = iw.getDefaultWriteParam();
		if (p.canWriteCompressed()) {
			p.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
			String[] t = p.getCompressionTypes();
			if (t.length > 0) {
				p.setCompressionType(t[0]);
				log("Compression " + t[0]);
				// System.out.println("Compression "+t[0]);
			}
			p.setCompressionQuality((float) compression);
		}

		IIOImage iioimage = new IIOImage(image, null, null);

		// als er een maximumgrootte is opgegeven, werkt enkel als er compressie
		// mogelijk is
		if (maxsize > 0 && p.canWriteCompressed()) {
			ByteArrayOutputStream bos;
			do {
				// eerste keer is met de standaard compressie
				bos = new ByteArrayOutputStream();
				iw.setOutput(ImageIO.createImageOutputStream(bos));
				iw.write(null, iioimage, p);
				// hierna verlagen we de compressie naargelang de grootte van
				// het bestand
				// de .90f is een veiligheidsmarge om het sneller te laten
				// verlopen
				// nu nog hardcoded, mogelijk te veranderen
				// er is wel een minimumcompressie van 0.001
				if (bos.size() > maxsize) {
					p.setCompressionQuality(p.getCompressionQuality() * maxsize
							* .90f / bos.size());
				}
				// minimumcompressie van 0.001
			} while (bos.size() > maxsize && p.getCompressionQuality() > 0.01f);
			// schrijf nu naar de outputstream
			bos.writeTo(os);
		} else {
			iw.setOutput(ImageIO.createImageOutputStream(os));
			iw.write(null, iioimage, p);
			os.flush();
		}
		iw.dispose();
	}

	public static void main(String[] args) {
		// ImageRequest test = new
		// ImageRequest("http://85.119.218.198/mediaserver/templates/tmf/default/images/","splash_top.png");
		
		ImageRequest test = new ImageRequest(
				"/home/geert/projects/sms/www/tmf/vodafonelive/test/tmf/images",
				"logo.png",1024*1024);
		try {
			BufferedImage img = test.readImage();
			System.out.println("found image " + img.getHeight() + " x "
					+ img.getWidth());
		} catch (IOException e) {
			e.printStackTrace();
		}

	}

}
